<?php defined('SYSPATH') OR die('No direct access allowed.');

/**
 * The main parser class performs the parsing of the template file.
 * 
 * Plugs are automatically loaded into the object.  The input of the
 * template file is read and parsed in this class.  Has the ability
 * to parse both closing and non-closing tags.
 */
class Parser_Core {
	
	// Plugs array - contains the names of the plugs
	protected $plugs = array();
	// Unknown plug name array - contains a set of unknown plug names
	protected $unknown_plug_name = array();
	// Input buffer contents
	protected $input_buffer = NULL;
	// Write flag
	protected $write = TRUE;
	// Special handler for 'case' plug
	protected $first_case = TRUE;
	
	/**
	 * Factory method - allows method chaining.
	 *
	 * @param  string  Template name ($template_name)
	 * @param  string  Buffer data   ($data)
	 * @return object
	 */
	public static function factory($template_name = FALSE, $data = NULL, $write = TRUE)
	{
		return new Parser($template_name, $data, $write);
	}
	
	/**
	 * Construct handles loading the plugs and creating the output buffer.
	 * 
	 * @param $template_name  string  Name of the template ($template_name)
	 * @param $data           string  HTML / PHP data      ($data)
	 */
	public function __construct($template_name = FALSE, $data = NULL, $write = TRUE)
	{
		// Load the plugs
		$this->load_plugs();
		
		// Fill the output buffer with contents of file, or data if passed through
		$this->input_buffer = ($template_name) ? file_get_contents(Kohana::find_file('views', $template_name)) : $data;
		
		$this->write = $write;
	}
	
	/**
	 * Handles parsing of the view file
	 * 
	 * @param  string  Output filename - written to the parsed directory ($file_name)
	 */
	public function parse($output_file_name)
	{
		// Setup delimiters
		$delim_left = Kohana::config('kotemp.delimiter.left');
		$delim_right = Kohana::config('kotemp.delimiter.right');
		
		// Create the matches
		$matches = preg_split("~".$delim_left."(/?)([^".$delim_right."]*)".$delim_right."~", $this->input_buffer, -1, PREG_SPLIT_DELIM_CAPTURE);
		
		// Create a node stack for our matches
		// This allows us to push, pop, and return the last node on the stack
		$node_stack = new Nodestack;
		
		// Create a root node and push it into the stack
		$node_stack->push(new Rootnode(''));
		
		// Create a counter so we know what match index we're on
		$counter = 0;
		
		// Split the matches so we can find nodes
		// 0 = text
		//-------These two represent a tag-------//
		// 1 = closing template tag or nothing
		// 2 = everything within the template tag delimiter
		foreach ($matches as $key => $match)
		{
			$node_key = $key % 3;
			
			$parent = $node_stack->last();
			
			switch ($node_key)
			{
				// Handles text
				case 0:
				{
					// Push the text onto the stack within the parent node
					$parent->add_text_to_buffer($match);
					
					$counter++;
					break;
				}
				// Detects closing and nonclosing tags
				case 1:
				{
					$closing = FALSE;
					
					// Determine if it's a closing tag or not and if it's a known tag
					if ($match == '/')
					{
						$closing = TRUE;
						
						// Handler for an unknown closing tag
						if (!array_key_exists($matches[$counter+1], $this->plugs))
						{
							$this->unknown_plug_name[] = $matches[$counter+1];
							
							$counter++;
							break;
						}
						
						// Need to pop the current node off the stack
						// otherwise we will end up adding text to itself
						$node = $node_stack->pop();
						
						// New parent is the last node in the stack
						$parent = $node_stack->last();
						
						// A closing tag requires the same name as its opening tag
						if($node->get_name() != $matches[$counter+1])
						{
							// Reset the stack to where it was
							$node_stack->push($node);
							$parent = $node_stack->last();
							
							// Add in an HTML error
							$parent->add_text_to_buffer('<!-- BAD CLOSING TAG FOR [' . $plug_name . '] -->');
						}
						else
						{
							//echo "Adding text: ".$node->get_name()."<br/>";
							// Parse the tag and add it to the parent node's buffer
							$parent->add_text_to_buffer($node->parse_buffer());
						}
					}
					
					$counter++;
					break;
				}
				// Handles contents of the tags
				case 2:
				{
					// Split up the actual tag
					$tag_parts = preg_split("/[\s]+/", $match, -1, PREG_SPLIT_DELIM_CAPTURE);
					
					if (substr($tag_parts[0], 0, 1) == '$')
					{
						$plug_name = 'print';
					}
					else
					{
						// Try to get the tag name
						$plug_name = trim(strtolower($tag_parts[0]));
					}
					
					// Handles non-closing tags
					if (!$closing)
					{
						// Return the current node based on the tag name
						$node = $this->get_plug_handler($plug_name);
						
						// Switch tag is a bit wonky - handles the first case
						if ($node->get_name() == 'case' && $this->first_case)
						{
							$node->is_first($this->first_case);
							$this->first_case = FALSE;
						}
						elseif ($node->get_name() == 'switch')
						{
							$this->first_case = TRUE;
						}
						
						// Add the contents of the node
						$node->push_clause($tag_parts, $plug_name);
						
						// Parse non-closing nodes without putting it on the stack
						if (is_subclass_of($node, 'Nonclosingnode'))
						{
							$parent->add_text_to_buffer($node->parse_buffer());
						}
						// Normal opening tag
						else
						{
							$node_stack->push($node);
						}
					}
					elseif ($this->unknown_plug_name)
					{
						$parent->add_text_to_buffer('<!-- UNKNOWN CLOSING PLUG [' . array_pop($this->unknown_plug_name) . '] -->');
					}
					
					$counter++;
					break;
				}
			}
		}
		
		// Write to template file
		if ($this->write)
		{
			// Write the output buffer
			kotemp::write($output_file_name, $node_stack->pop()->get_output_buffer());
		}
		// Return the output buffer
		else
		{
			return $node_stack->pop()->get_output_buffer();
		}
	}
	
	/**
	 * Gets plug object.
	 * 
	 * @param  string  Plug name - same as filename ($plug_name)
	 * @return object  Plug object
	 */
	protected function get_plug_handler($plug_name)
	{
		if (isset($this->$plug_name))
		{
			return clone $this->$plug_name;
		}
		
		return clone $this->unknown;
	}
	
	/**
	 * Loads the plugs as part of the parser object.
	 */
	protected function load_plugs()
	{
		// Get all the plugs
		if ($handle = opendir(MODPATH.'kotemp/libraries/plugs'))
		{
			while (false !== ($file = readdir($handle)))
			{
				if ($file != "." && $file != ".." && preg_match("/._/", $file) != TRUE)
				{
					// Strip the .php
					$file = str_replace(".php", "", $file);
					// Store them in the plugs array
					$this->plugs[$file] = "Plug_".ucfirst($file);
				}
			}
			closedir($handle);
		}
		
		foreach ($this->plugs as $file => $plug)
		{
			require_once(Kohana::config('kotemp.plug_dir').$file.EXT);
			
			if (class_exists($plug))
			{
				$this->$file = new $plug($file);
			}
		}
	}
	
	/**
	 * Magic toString method
	 * 
	 * @return  string  Contents of the input buffer
	 */
	public function __toString()
	{
		return (string) $this->input_buffer;
	}
}